crackmeUK 解析手引き その1
今までの asm 製 crackme とは違い、VC - MFC 製なので、チェッカの捕捉にちょっと時間が掛かると思います。 ところで皆さん、crackme_UK を解けた方はどれだけいらっしゃいますか? 今回は都合上、4回にわけて説明したいと思います。 まずデバッガチェック・UPX 対策部分のみの解説となります。 今回の crackme は『 eagle0wl's crackme 』と比べるとかなり難しいものとなっていますが、 今までの crackme を全て解けたのなら、この crackme_UK も出来るはずです。 皆さん、頑張りましょう。 この crackme ですが、デバッガ対策・ UPX 対策がなされているそうです。 本体を調べてみると UPX でパックされています。まずアンパックしてみましょう。 アンパックの手順はわかりますね。それではアンパックされた crackme を普通に起動してみましょう。 『Incorrect File. Down Load Again!!!』と表示されてしまいました。 アンパック前は問題なく起動できたのに、なぜこんなことになるのでしょうか? ここでまず躓いてしまいました・・・。 これは恐らく実行ファイル(つまり自分自身)に対して改竄チェックを行っているのではないかと思われます。 ここから OllyDbg の出番です。 crackme を読み込んでから、CreateFileA にブレークポイントを仕掛けてみましょう。(2つとも BP を仕掛ける) 004011FE mov ebx, dword ptr ds:[<&KERNEL32.CreateFileA>] 004141A7 call near dword ptr ds:[<&KERNEL32.CreateFileA>] さて、ブレークしましたか? ブレークした箇所は 004011FE |> 8B1D 80C14100 mov ebx, dword ptr ds:[<&KERNEL32.CreateFileA>] です。これは ebx レジスタに API,CreateFileA のアドレスを格納しているだけで 実行はまだ行っていません。その下をトレースしながら見てみると、 00401204 |. 6A 00 push 0 ; /hTemplateFile = NULL 00401206 |. 68 80000000 push 80 ; |Attributes = NORMAL 0040120B |. 6A 03 push 3 ; |Mode = OPEN_EXISTING 0040120D |. 6A 00 push 0 ; |pSecurity = NULL 0040120F |. 6A 03 push 3 ; |ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE 00401211 |. 8D4C24 20 lea ecx, dword ptr ss:[esp+20] ; | 00401215 |. 68 000000C0 push C0000000 ; |Access 典型的な SoftICE チェッカですね。 UPX 対策箇所を探すつもりでしたが、デバッガチェック箇所を探し当てることが出来たみたいです。 ここで注目して貰いたいのは、CreateFileA にブレークポイントを仕掛けたはずなのに CreateFileA がコールされているアドレス 0040121B にはブレークポイントが仕掛けられていません。 よく見ると call near ebx と、ebx レジスタ内部の値を参照しています。 ebx の値が決まるまで CreateFileA が呼び出されるとは断定できないため、ブレークポイントが 仕掛けられなかったのだと思われます。 (でも右側に CreateFileA と引数の表示がされているのはなぜでしょうか。誰か知ってる方教えて下さい) それではデバッガチェック部分を当たってみましょう。 0040121A |. 51 push ecx ; |FileName = "\\.\ICE" <-- 注目!! 0040121B |. FFD3 call near ebx ; \CreateFileA <-- BP は仕掛けられていない! 0040121B まで各自 F8 を押してそこまで進めて下さい。そうすると0040121B でブレークしているはずです。 それではデバッガチェック部分を当たってみましょう。 0040121B |. FFD3 call near ebx ; \CreateFileA 0040121D |. 83F8 FF cmp eax, -1 00401220 |. 74 10 je short CRACKME_.00401232 皆さんは OllyDbg を使用しているはずなので、SoftICE は検出されず je 命令でジャンプするはずですが、 ここではあえてゼロフラグを反転させることで SoftICE が検出されたことにして下さい。 SoftICE が検出された場合は、戻り値 1 をセットして ret 命令で関数を抜けています。 ではなぜ戻り値は 1 とわかるのでしょうか? je 命令でジャンプしなかったときに実行される命令を見てみましょう。 0040122B |. B0 01 mov al, 1 ; 戻り値 1 をセット 0040122D |. 5B pop ebx 0040122E |. 83C4 20 add esp, 20 00401231 |. C3 retn なぜ戻り値がわかるのかと言いますと、殆どのコンパイラは関数の戻り値を eax レジスタに 格納するようにしているからです(★解析を行う上でのポイント)。 ここで SoftICE が検出されなかったら SoftICE(NT版)のチェッカがあるのですが、これは自分で 確認してみて下さい。 今回は ゼロフラグを反転させて SoftICE が検出されたことにしました。関数を抜けるとここに現れます。 004010CF . E8 BC000000 call CRACKME_.00401190 <-- SoftICE チェック関数 004010D4 . 84C0 test al, al <-- ここに居る 004010D6 . 74 12 je short CRACKME_.004010EA 004010D8 . 68 2C214200 push CRACKME_.0042212C ; ASCII "I find "soft ice". Kill Soft Ice !!!!!" al の値は 1 で test al, al の結果、ゼロフラグが立たないので次のジャンプ命令が実行されず、 SoftICE 検出を示す文字列が参照されてしまいます。 ここでは、 test al, al を xor eax, eax に変更することで SoftICE チェッカを殺すことにしましょう。 SoftICE チェッカを抜けたら 004010EA > E8 E1020000 call CRACKME_.004013D0 <- ここに来る 004010EF . 84C0 test al, al 004010F1 . 74 7F je short CRACKME_.00401172 ここに来るはずです。 call 分の後に比較・分岐があるので非常に怪しいですが、とりあえず F8 で素通りしてみましょう。 すると、ブレークポイントに引っかかって停止しました。CreateFileA です。 ブレークしましたか? 一体どんなファイルを調べようとしているのでしょうか。デバッグ画面右下のスタック領域部分を見て下さい。 0065F700 0065F870 |FileName = "C:\(中略)\abc.dll" 0065F704 80000000 |Access = GENERIC_READ CreateFileA の引数が見えます。 非常に読みやすくて良いですね。OllyDbg 最強! 参照されているファイルは abc.dll です。この abc.dll とは何なのかは疑問ですが、とりあえず置いておきます。 とりあえず F8 を連打してトレースを続けてみましょう。 これであの dll ファイルを参照しているんですね 004014DD |. E8 EA2B0100 call CRACKME_.004140CC ; \CRACKME_.004140CC 004014E2 |. 85C0 test eax, eax <-- ココに出る 004014E4 |. 0F84 99000000 je CRACKME_.00401583 004014EA |. B9 41000000 mov ecx, 41 ここの関数はちょっと長めですが、トレースはしないでズズズーっと下にスクロールしてみましょう。 すると、 00401583 |> 68 88214200 push CRACKME_.00422188 ; ASCII "Incorrect File. Down Load Again!!!" なんと、エラー表示の文字列を見つけてしまいました。 改竄チェックルーチンで間違いなさそうです。 ここまで追いつかない方は、文字列参照でショートカットできます。 今回は勉強の為、「最初から文字列参照でやればいいだろ!!」とかの突っ込みは勘弁して下さい。 さて、エラー表示の文字列参照部分があるということは、比較分岐命令が有るはずです。 上へ上へと眺めてみると、一ヶ所だけありました。 00401542 |. 3BD0 cmp edx, eax 00401544 |. 75 3D jnz short CRACKME_.00401583 ここは文字列参照されているアドレス 00401583 にカーソルを合わせて CTRL + R でもいいです。 非常に怪しいですね。edx と eax の値が違っているとジャンプしてしまい、エラー表示の文字列が 参照されてしまいますので、cmp edx, eax を cmp eax, eax に変更してしまいましょう。 変更はできましたか? では【変更した命令部分に BP を仕掛けてから】 F9 を押して実行してみましょう。 すると、CreateFileA でブレークしました。スタックを参照してみましょう。すると 0065F684 0065F974 |FileName = "C:\(中略)\CRACKME.EXE" 0065F688 80000000 |Access = GENERIC_READ 自分自身が参照されています。改竄チェックなので当然ですね。 ここから読み込みが始まるのですが、この内部を説明すると非常に骨が折れるので割愛します。 興味のある方は自分で調べてみて下さい。 F9 を押してみると、先ほどブレークポイントを仕掛けた変更済の比較命令でブレークするはずです。 00401542 3BC0 cmp eax, eax <- ここでブレーク 00401544 |. 75 3D jnz short CRACKME_.00401583 ここでは変更済なのでジャンプせず、UPX 対策は突破されたことになります。 eax レジスタと、比較されるはずだった edx レジスタの中身に注目して下さい。 eax = B2E8B9FC, edx = ACCF4C2C となっていました。 abc.dll が参照されたことを 思い出して下さい。ちょっと abc.dll の中身を参照してみましょう。 [offset]: +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F 0123456789ABCDEF ---------------------------------------------------------------------------- 00000000: 2C 4C CF AC ,Lマャ x86 系 CPU はリトルエンディアン形式なので逆読みすると ACCF4C2C 、 これは edx レジスタに入っていた値と同じです!! ファイルの改竄チェックで一番有名なものに CRC(巡回冗長符号)というものがあります。 本 crackme の UPX 対策チェッカで用いられているのは CRC です。 これはファイル等まとまったデータからある一定の値(大抵 32bit 分の値)を生成するもので、 今回は、あらかじめ用意されている UPX 圧縮された改竄前の crackme から生成された CRC 値と、 今回読み込んだ実行ファイルから生成された CRC 値を比較していたわけです。 しかし、なぜ abc.dll なるファイルが必要だったのでしょうか? これは、CRC 値作成にあたり、実行ファイルの何処から何処までを対象にしているかが問題なのです。 もし実行ファイルの中に改竄前のファイルから生成された CRC 値 を埋め込んでいるのだとすると、 その CRC 部分を改竄チェックの対象に含めてしまうと値が滅茶苦茶になってしまいます。 実行ファイルに埋め込むとなると実装が難しくなるので、別ファイルにしたというわけですね。 CRC についての講釈はここまでにして、現段階でのパッチは以下のようになります。 FILENAME CrackMe.exe * Anti UPX checker 00001543: D0 C0 * Anti-Anti SoftICE 000010D4: 84 33 この手の Anti-Debug, Anti-UPX 解除は一般的な国産ソフトでは殆ど見られません。 Anti-UPX については海外の crackme では見られるようです。 Anti-Debug が使われている代表的なソフトは、個人情報を送信していることが問題となった 某 ftp ソフトです。 今回の Anti-Debug 処理と同じ方法が使われていたのですが、 その対象がデバッガではなく、キージェネに対してでした。 つまり、キージェネ自体をブラックリスト化されています。 立ち上げていなければチェックのしようがないので、キーを生成してからそのキーをメモしてから Keygen を終了し、対象ソフトを立ち上げれば OK のようです。 さて、本題に戻りましょう。CRC チェッカ部分を殺す場所で停止していますが、 00401542 3BC0 cmp eax, eax <- edx, edx を書き換えている 00401544 |. 75 3D jnz short CRACKME_.00401583 F8 連打で(CTRL + F9 でもよい) ret 命令まで進み関数を抜けてみましょう。すると、 004010EA > E8 E1020000 call CRACKME_.004013D0 <-- ファイル改竄チェック関数 004010EF . 84C0 test al, al <-- ここに出ます 004010F1 . 74 7F je short CRACKME_.00401172 004010F3 . 8D4424 0C lea eax, dword ptr ss:[esp+C] 004010F7 . C74424 0C 0100>mov dword ptr ss:[esp+C], 1 004010FF . 50 push eax ; /lParam 00401100 . 68 C0124000 push CRACKME_.004012C0 ; |Callback = CRACKME_.004012C0 00401105 . FF15 38C44100 call near dword ptr ds:[<&USER32.Enum>; \EnumWindows 0040110B . 8B4424 0C mov eax, dword ptr ss:[esp+C] <- ここに BP を仕掛けてね 矢印が指している部分に出ました。ここでは内部にパッチを当てたので、ジャンプせず処理が継続されます。 その下には EnumWindows が見えます。 実はこれがにっくき OllyDbg チェックルーチンなのです。 EnumWindows の引数 Callback = CRACKME_.004012C0 とありますが、これが EnumWindows が呼び出そうとしている コールバック関数の開始アドレスです。 crackme #15 でも同じやつがありましたね。 F7 でトレースしてもコールバック関数内部にはジャンプしないので、CTRL + G でアドレス 004012C0 に 移動して BP を仕掛けてください。 でもその前に EnumWindows を抜けた後もトレースを続けたいので、アドレス 0040110B に BP を仕掛けてから 上記の動作を行って下さい。 実行するとコールバック関数内部で止まるはずです。 F8 を連打してトレースすると、#15 と同じように現在デスクトップ上に存在するウィンドウのタイトルバーの 文字列を取得して、NG ワードチェックを行っているようです。 00401304 . 8B15 B0204200 mov edx, dword ptr ds:[4220B0] ; CRACKME_.004220E4 <- "Olly" が入る 0040130A . 8D4424 04 lea eax, dword ptr ss:[esp+4] 0040130E . 52 push edx 0040130F . 50 push eax 00401310 . E8 8B350000 call CRACKME_.004048A0 ; タイトルバー文字列から "Olly" をサーチ 00401315 . 83C4 08 add esp, 8 00401318 . 85C0 test eax, eax 0040131A . 5F pop edi 0040131B . 75 6D jnz short CRACKME_.0040138A ; 見つかったらアウト 最初のチェックはこんな感じです。 同様に、"W32", "Dump", "Moniter" をタイトルバーから探し、もし一致したらデバッガが検出されたことになり、 エラー処理に飛ばすようにしています。"Moniter" の綴りが違うような気がしますが UK さんに確認したところ、 WebCheckMonitor というやつが常駐していると誤認されてしまうため、わざと変えてみたそうです。 結果、test eax, eax を xor eax, eax に変更すればよさそうです。変える場所は計4ヶ所。 00401318, 00401331, 00401348, 00401360 です。(命令はいずれも test eax, eax) 変更が完了したらコールバック関数の先頭に仕掛けた BP (アドレス 004012C0) は解除して下さい。 EnumWindows が捕まえたウィンドウの数だけ処理を繰り返すのでそのたびにブレークして大変だからです。 さて、F9 を押すと、先ほど BP を仕掛けた EnumWindows を抜けたところの命令でブレークします。 いよいよ最後のデバッガチェック部分です。 0040110B . 8B4424 0C mov eax, dword ptr ss:[esp+C] <-- ここで停止 0040110F . 85C0 test eax, eax 00401111 . 7C 5F jl short CRACKME_.00401172 <-- デバッガチェックを潰したのでジャンプせず 00401113 . FFD7 call near edi <-- これは一体?? 非常に気になる命令は call near edi です。これは一体なんでしょう? ここでちょっとレジスタについての、いい話をしておきましょう。 普通 call 命令を抜けると各種レジスタの値は変わってしまうのですが(eax レジスタには戻り値が格納されている)、 ebx, esi, edi レジスタの値については call 命令内部で push'n pop されているためその値は保持されています。 ですから、頻繁に値の変わる eax, ecx, edx レジスタとは違って ebx, esi, edi レジスタに入っています。 その値は、関数内部で長く使われるもの、すなわち結構重要そうであると解釈できるはずです(★かなり重要)。 これを覚えておくと、EASY のパス出しで結構楽をする事ができるので要チェック! 話は戻りまして、call near edi についてですが、この関数内部で edi レジスタに値を移している部分を さかのぼって探してみましょう。 すると、ありました。デバッガチェック部分の最初の方です。 004010C5 . 8B3D 24C24100 mov edi, dword ptr ds:[<&KERNEL32.GetTickCount>] 004010CB . FFD7 call near edi ; [GetTickCount] 004010CD . 8BD8 mov ebx, eax ; ebx に時間情報を格納 edi の正体は GetTickCount でした。この関数は Windows が起動されてから経過した時間を ミリ秒単位で返すAPI です(引数無し)。ebx に時間情報を格納していますが、edi の正体が分かったところで 先ほどの call near edi をもう一度見てましょう。 00401113 . FFD7 call near edi <-- GetTickCount(2度目) 00401115 . 2BC3 sub eax, ebx ; eax - ebx(ebx には時間情報) 00401117 . 83F8 64 cmp eax, 64 ; 64h = 100 0040111A . 76 0F jbe short CRACKME_.0040112B ; eax <= 100 なら OK 0040111C . 68 10214200 push CRACKME_.00422110 ; ASCII "Too Slow. Stop Debugger!!!!" GetTickCount で時刻情報を取得し、その値は eax レジスタに格納されています。 次に sub eax, ebx が行われているわけですが、ebx にはデバッガチェックの最初の方で GetTickCount(1度目)で取得した値が入っています。 ということは、【GetTickCount の1度目と2度目の間にある命令の実行時間を算出している】 ということが分かるはずです。 そして cmp eax, 64 で、実行時間が 100 ミリ秒以内であるかどうかを調べ、そうであれば デバッガチェックは終了となります。これは一体何を調べようとしているのでしょうか? よほど遅い CPU でもなければデバッガチェックルーチンは 100 ミリ秒以内で実行できるはずです。 しかし例外があります。それはデバッガでトレースしている場合です。トレースするということは 実行が一時停止状態になるわけですから、実行時間は 100 ミリ秒をゆうに超えてしまいます。 ということは、これはアンチトレース処理であるということができます。 時間差攻撃を潰す訳ですね。 この対処方法は簡単です。cmp eax, 64 を xor eax, eax に変えるだけです。 これで、ようやくデバッガチェック部分は終了しました。 以上をまとめたパッチは、 FILENAME CrackMe.exe * Anti UPX checker 00001543: D0 C0 * Anti-Anti SoftICE 000010D4: 84 33 * Anti-Anti Debugger 00001318: 85 33 00001331: 85 33 00001348: 85 33 00001360: 85 33 00001378: 85 33 * Anti-Anti Trace 00001115: 2B 33 00001116: C3 C0 これは一例です。 とりあえず今回はデバッガチェック部分のみで終わります。 次回予定、・EASY の攻略、・NORMAL の攻略、・DIFFICULT の分析 DIFFICULT だけ "分析" となっていますが、その理由はやってみればわかるはずです。